#pragma once

// 日志落地模块的实现
/*
    1. 抽象落地基类
    2. 派生子类(根据不同的落地方向进行派生)
    3. 使用工厂模式进行创建与分离
*/

#ifndef _M_SINK_H__
#define _M_SINK_H__

#include "util.hpp"
#include <cassert>
#include <memory>
#include <fstream>
#include <sstream>

namespace mylog
{
    // 基类
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;

        virtual ~LogSink()
        {
        }
        // 日志落地函数，子类实现
        virtual void log(const char *data, size_t len) = 0;
    };

    // 落地方向：标准输出
    class StdOutSink : public LogSink
    {
    public:
        void log(const char *data, size_t len)
        {
            // 不能直接cout，因为cout以/n结束输出
            std::cout.write(data, len);
        }
    };

    // 落地方向：指定文件
    class FileSink : public LogSink
    {
    public:
        // 构造时传入文件名，并打开文件，用操作句柄管理起来
        FileSink(const std::string &filename)
            : _filename(filename)
        {
            // 要先创建文件的路径
            mylog::util::File::CreateDirectory(mylog::util::File::path(_filename));
            // 打开文件
            _ofs.open(_filename, _ofs.binary | _ofs.app);
            assert(_ofs.is_open());
        }

        void log(const char *data, size_t len)
        {
            _ofs.write(data, len);
            assert(_ofs.good());
        }

        ~FileSink()
        {
            // 关闭文件
            _ofs.close();
        }

    private:
        std::string _filename;
        std::ofstream _ofs; // 操作句柄
    };

    // 落地方向：滚动文件
    class RollBySizeSink : public LogSink
    {
    public:
        // 构造时传入文件名，并打开文件，用操作句柄管理起来
        RollBySizeSink(const std::string &basename, size_t max_size)
            : _basename(basename), _max_size(max_size), _cur_size(0), _count(0)
        {
            std::string filename = createNewFile();
            // 要先创建文件的路径
            mylog::util::File::CreateDirectory(mylog::util::File::path(filename));
            // 打开文件
            _ofs.open(filename, _ofs.binary | _ofs.app);
            assert(_ofs.is_open());
        }
        // 写入前判断，超过最大大小就创建新文件
        void log(const char *data, size_t len)
        {
            if (_cur_size + len > _max_size)
            {
                // 要先关闭原先的文件
                _ofs.close();
                // 创建新文件
                std::string filename = createNewFile();
                // 打开文件
                _ofs.open(filename, _ofs.binary | _ofs.app);
                assert(_ofs.is_open());
                _cur_size = 0;
            }
            _ofs.write(data, len);
            assert(_ofs.good());
            _cur_size += len;
        }

        ~RollBySizeSink()
        {
            _ofs.close();
        }

    private:
        // 进行大小判断，超过指定大小则创建新文件
        std::string createNewFile()
        {
            time_t cur = util::Date::now();
            struct tm t;
            // 将时间戳转换成时间结构体
            localtime_r(&cur, &t);
            // 生成日志文件名
            std::stringstream filename;
            filename << _basename;
            filename << t.tm_year + 1990;
            filename << t.tm_mon + 1;
            filename << t.tm_mday;
            filename << t.tm_hour;
            filename << t.tm_min;
            filename << t.tm_sec;
            filename << "-";
            filename << _count++;
            filename << ".log";
            return filename.str();
        }

    private:
        std::string _basename;
        std::ofstream _ofs; // 操作句柄
        size_t _max_size;   // 记录最大大小，当前文件超过最大大小，就创建新文件
        size_t _cur_size;   // 记录当前文件大小
        size_t _count;      // 文件计数器
    };

    // 日志落地工厂——简单工厂
    class SinkFactory
    {
    public:
        // 第一个参数是落地模式
        // 第二个参数是落地模式需要的构造参数
        template <typename SinkType, typename... Args>
        static LogSink::ptr create(Args &&...args)
        {
            // forward完美转发
            // 最后的...是将可变参数包展开
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif